﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;

namespace GeneralAdaptiveFramework
{
    public delegate void UpdateComponent(String filePath, String name);
    public delegate void UpdateConnection(String sourceComponent, String targetComponent, String sourcePort, String targetPort);
    public class ComponentMgr : CMSInterface
    {
        protected ArrayList componentList = new ArrayList();
        /**
         * Add a component from file to program
         */
#if DEBUG
        public void addComponent(String filePath, String name)
        {
            Component component = null;
            switch (name)
            {
                case "Escape":
                    component = new Escape(name);
                    stop();
                    break;
                case "Patrol":
                    component = new Patrol(name);
                    stop();
                    break;
                case "Collect":
                    component = new Collect(name);
                    break;
                case "Escape Analyze":
                    component = new EscapeAnalyze(name);
                    break;
                case "Patrol Analyze":
                    component = new PatrolAnalyze(name);
                    break; 
                case "Escape Decide":
                    component = new EscapeDecide(name);
                    break;
                case "Patrol Decide":
                    component = new PatrolDecide(name);
                    break;
                case "Act":
                    component = new Act(name);
                    break;
                default :
                    Console.WriteLine(name + ": not found1");
                    break;
            }
            if (component != null)
                registerComponentInfo(component);
        }
#else
        public void addComponent(String filePath, String name)
        {
            bool confirmed = confirmFile(filePath);
            if (!confirmed)
                throw new ComponentMgrException(filePath + ": file not found");

            // get class
            Component component = ClassLoader.getInstance(filePath);
            component.name = name;
            // register component info
            registerComponentInfo(component);
        }
#endif

        /**
         * Remove component from program
         */
        public void removeComponent(String name)
        {
            // step 3: get component
            Component component = getComponent(name);
            if (component == null)
                throw new ComponentMgrException(name + ": component not found");
            // step 4: component remove itself

            // step 5: remove component from list -- remove component info
            removeComponentInfo(component);
        }

        /**
         * Connect two components by ports
         */
        public void connectComponent(String sourceComponent, String targetComponent, String sourcePort, String targetPort)
        {
            // step 3: get the source component
            Component source = null, target = null;
            source = getComponent(sourceComponent);
            if (source == null)
                throw new ComponentMgrException(sourceComponent + ": component not found");

            // step 4: get provide ports
            ArrayList providePorts = source.getProvidePorts();
            ProvideInterface provideInterface = null;
            foreach (ProvideInterface p in providePorts)
                if (p.name == sourcePort)
                {
                    provideInterface = p;
                    break;
                }
            if (provideInterface == null)
                throw new ComponentMgrException(sourcePort + ": port not found");

            Object provideCondition = null, requireCondition = null;
            // step 5: get port condition
            provideCondition = provideInterface.getPortCondition();

            // step 6: get the target component
            target = getComponent(targetComponent);
            if (target == null)
                throw new ComponentMgrException(targetComponent + ":component not found");

            // step 7: get require ports
            ArrayList requirePorts = target.getRequirePorts();
            RequireInterface requireInterface = null;
            foreach (RequireInterface r in requirePorts)
                if (r.name == targetPort)
                {
                    requireInterface = r;
                    break;
                }

            // step 8: get port condition
            if (requireInterface == null)
                throw new ComponentMgrException(targetPort + " port not found");
            requireCondition = requireInterface.getPortCondition();

            // check connection condition between create a connection

            if (!((bool)requireCondition == false && (bool)provideCondition == false))
            {
                throw new ComponentMgrException("Either '" + sourcePort + "' or '" + targetPort + "' is busy.");
            }

            // step 9: create connection
            Connection connection = new Connection();
            connection.create(provideInterface, requireInterface);

            // step 10: register connection
            provideInterface.registerConnection(connection);

            // step 11: register connection
            requireInterface.registerConnection(connection);
        }

        public void disconnectComponent(string sourceComponent, string targetComponent, string sourcePort, string targetPort)
        {
            // step 3: get the source component
            Component source = getComponent(sourceComponent);
            if (source == null)
                throw new ComponentMgrException(sourceComponent + ": component not found");

            // step 4: get provide ports
            ArrayList providePorts = source.getProvidePorts();
            ProvideInterface provideInterface = null;
            foreach (ProvideInterface p in providePorts)
                if (p.name == sourcePort)
                    provideInterface = p;
            if (provideInterface == null)
                throw new ComponentMgrException(sourcePort + ": port not found");

            // step 5: get connection
            Connection connection = provideInterface.getConnection();
            if ((bool)provideInterface.condition == false)
            {
                throw new ComponentMgrException(sourcePort + " is free and does not need to be disconnected");
            }
            else
            {

                // step 6: get target info
                string targetInfo = connection.getRequireInterfaceInfo();


                // step 7: remove connection
                if (targetInfo == targetPort)
                {
                    provideInterface.removeConnection();
                }

                // step 8: get the source component
                Component target = getComponent(targetComponent);
                if (target == null)
                    throw new ComponentMgrException(targetComponent + ":component not found");

                // step 9: get target ports
                ArrayList targetPorts = target.getRequirePorts();
                RequireInterface requireInterface = null;
                foreach (RequireInterface r in targetPorts)
                    if (r.name == targetPort)
                        requireInterface = r;
                if (requireInterface == null)
                    throw new ComponentMgrException(targetPort + " port not found");

                // step 10: remove connection
                requireInterface.removeConnection();

                // step 11: remove Connection object
                connection.remove();
            }
        }

        /**
         * Get a component by a given name
         */
        public Component getComponent(String name)
        {
            foreach (Component c in componentList)
                if (c.name == name)
                    return c;
            return null;
        }

        public void registerComponentInfo(Component component)
        {
            // provide a way to component update the component manager
            component.addComponent += new UpdateComponent(this.addComponent);
            component.addConnection += new UpdateConnection(this.connectComponent);
            component.removeConnection += new UpdateConnection(this.disconnectComponent);

            // add the component to list
            componentList.Add(component);

            // execute
            if (component is ActiveComponent)
                component.execute();
        }

        /**
         * Remove component and all connections connected with it
         */
        public void removeComponentInfo(Component component)
        {
            // remove provide connections
            ArrayList providePorts = component.getProvidePorts();
            foreach (ProvideInterface p in providePorts)
            {
                Object condition = p.getPortCondition();
                if ((bool)condition == true)
                {
                    // p is currently connected with other port
                    Connection connection = p.getConnection();
                    string providePort = connection.getProvideInterfaceInfo();
                    string requestPort = connection.getRequireInterfaceInfo();
                    Component targetComponent = getTargetComponentHasConnection(connection);

                    disconnectComponent(component.name, targetComponent.name, providePort, requestPort);
                }
            }

            // remove require connections
            ArrayList requirePorts = component.getRequirePorts();
            foreach (RequireInterface r in requirePorts)
            {
                Object condition = r.getPortCondition();
                if ((bool)condition == true)
                {
                    // r is currently connected
                    Connection connection = r.getConnection();
                    string providePort = connection.getProvideInterfaceInfo();
                    string requestPort = connection.getRequireInterfaceInfo();
                    Component sourceComponent = getTargetComponentHasConnection(connection);

                    disconnectComponent(sourceComponent.name, component.name, providePort, requestPort);
                }
            }
            componentList.Remove(component);
        }

        /**
         * Confirm whether or not a file is existed
         */
        public bool confirmFile(String filePath)
        {
            return File.Exists(filePath);
        }

        /**
         * Find the component having the given connection as the required port
         */
        private Component getTargetComponentHasConnection(Connection connection)
        {
            foreach (Component c in componentList)
            {
                ArrayList requirePorts = c.getRequirePorts();
                foreach (RequireInterface requirePort in requirePorts)
                {
                    Connection tmpConnection = requirePort.getConnection();
                    if (tmpConnection == connection)
                        return c;
                }
            }
            return null;
        }

        /**
         * Find the component having the given connection as the required port
         */
        private Component getSourceComponentHasConnection(Connection connection)
        {
            foreach (Component c in componentList)
            {
                ArrayList providePorts = c.getProvidePorts();
                foreach (ProvideInterface providePort in providePorts)
                {
                    Connection tmpConnection = providePort.getConnection();
                    if (tmpConnection == connection)
                        return c;
                }
            }
            return null;
        }

#if DEBUG
        public void stop()
        {
            foreach (Component c in componentList)
                if (c is ActiveComponent)
                {
                    ((ActiveComponent)c).stop();
                    removeComponent(c.name);
                    break;
                }
        }
#endif
    } 
}
